{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "许多AI应用需要在多个交互中共享上下文(会话历史)。在LangGraph中，这种内存可以通过线程级持久性添加到任何StateGraph。在创建任何LangGraph图时，可以通过在编译graph时添加一个检查点来设置graph的状态持久性。\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "f2b64f5446f56698"
  },
  {
   "cell_type": "code",
   "outputs": [],
   "source": [
    "\n",
    "from dotenv import load_dotenv\n",
    "from langchain_community.llms.tongyi import Tongyi\n",
    "from langgraph.constants import START\n",
    "from langgraph.graph import StateGraph, MessagesState\n",
    "\n",
    "load_dotenv()\n",
    "\n",
    "llm = Tongyi()\n",
    "\n",
    "def call_model(state:MessagesState):\n",
    "    response = llm.invoke(state[\"messages\"])\n",
    "    return {\"messages\":response}\n",
    "\n",
    "builder = StateGraph(MessagesState)\n",
    "builder.add_node(\"call_model\", call_model)\n",
    "builder.add_edge(START, \"call_model\")\n",
    "graph = builder.compile()"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-12-02T09:41:58.832474Z",
     "start_time": "2024-12-02T09:41:58.824593Z"
    }
   },
   "id": "b3f96d010fb5137f",
   "execution_count": 9
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001B[1m Human Message \u001B[0m=================================\n",
      "\n",
      "hi! I'm bob\n",
      "================================\u001B[1m Human Message \u001B[0m=================================\n",
      "\n",
      "Hello Bob! It's nice to meet you. How can I assist you today? Feel free to ask me anything or let me know if you need help with any specific topics.\n",
      "================================\u001B[1m Human Message \u001B[0m=================================\n",
      "\n",
      "what's my name?\n",
      "================================\u001B[1m Human Message \u001B[0m=================================\n",
      "\n",
      "I'm sorry, but I don't know your name. Could you please tell me what you'd like to be called?\n"
     ]
    }
   ],
   "source": [
    "input_message = {\"type\": \"user\", \"content\": \"hi! I'm bob\"}\n",
    "for chunk in graph.stream({\"messages\": [input_message]}, stream_mode=\"values\"):\n",
    "    chunk[\"messages\"][-1].pretty_print()\n",
    "\n",
    "input_message = {\"type\": \"user\", \"content\": \"what's my name?\"}\n",
    "for chunk in graph.stream({\"messages\": [input_message]}, stream_mode=\"values\"):\n",
    "    chunk[\"messages\"][-1].pretty_print()"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-12-02T09:42:02.123935Z",
     "start_time": "2024-12-02T09:41:58.835110Z"
    }
   },
   "id": "51a5185bd31a6913",
   "execution_count": 10
  },
  {
   "cell_type": "markdown",
   "source": [
    "## Add persistence\n",
    "为了添加持久性，我们需要在编译图时传入一个[Checkpointer](https://langchain-ai.github.io/langgraph/reference/checkpoints/#langgraph.checkpoint.base.BaseCheckpointSaver)\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "fd63cab4a042f445"
  },
  {
   "cell_type": "code",
   "outputs": [],
   "source": [
    "import sqlite3\n",
    "from langgraph.checkpoint.sqlite import SqliteSaver\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "\n",
    "graph = builder.compile(checkpointer= MemorySaver())"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-12-02T09:46:46.618470Z",
     "start_time": "2024-12-02T09:46:46.614240Z"
    }
   },
   "id": "61ff0935e6c994cb",
   "execution_count": 20
  },
  {
   "cell_type": "markdown",
   "source": [
    "现在我们可以与代理进行交互，并发现它能够记住之前的消息！"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "7e9d7e6fc0ef390e"
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001B[1m Human Message \u001B[0m=================================\n",
      "\n",
      "hi! I'm bob\n",
      "================================\u001B[1m Human Message \u001B[0m=================================\n",
      "\n",
      "Hello Bob! It's nice to meet you. How can I assist you today? Feel free to ask me any questions or let me know if you need help with anything specific.\n"
     ]
    }
   ],
   "source": [
    "config = {\"configurable\": {\"thread_id\": \"123\"}}\n",
    "input_message = {\"type\": \"user\", \"content\": \"hi! I'm bob\"}\n",
    "\n",
    "for chunk in graph.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n",
    "    chunk[\"messages\"][-1].pretty_print()"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-12-02T09:46:50.359126Z",
     "start_time": "2024-12-02T09:46:48.367430Z"
    }
   },
   "id": "a3657c8446cc7aa6",
   "execution_count": 21
  },
  {
   "cell_type": "markdown",
   "source": [
    "继续进行会话"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "7daea199eed717b1"
  },
  {
   "cell_type": "code",
   "outputs": [],
   "source": [
    "input_message = {\"type\": \"user\", \"content\": \"what's my name?\"}\n",
    "for chunk in graph.stream({\"messages\": [input_message]}, config, stream_mode=\"values\"):\n",
    "    chunk[\"messages\"][-1].pretty_print()"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-12-02T09:42:04.756927Z",
     "start_time": "2024-12-02T09:42:04.756777Z"
    }
   },
   "id": "1ed76679abf98b80",
   "execution_count": null
  },
  {
   "cell_type": "markdown",
   "source": [
    "如果我们要开始新的对话，可以传入不同的 thread_id。所有的记忆都消失了！\n",
    "\n"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "7132d9b0b5b0ed99"
  },
  {
   "cell_type": "code",
   "outputs": [],
   "source": [
    "input_message = {\"type\": \"user\", \"content\": \"what's my name?\"}\n",
    "for chunk in graph.stream(\n",
    "    {\"messages\": [input_message]},\n",
    "    {\"configurable\": {\"thread_id\": \"2\"}},\n",
    "    stream_mode=\"values\",\n",
    "):\n",
    "    chunk[\"messages\"][-1].pretty_print()"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "d5391c77ba00aee6",
   "execution_count": null
  },
  {
   "cell_type": "markdown",
   "source": [
    "## sqlite"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "75ae3495800dd3e2"
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "ename": "InvalidStateError",
     "evalue": "Synchronous calls to AsyncSqliteSaver are only allowed from a different thread. From the main thread, use the async interface.For example, use `await checkpointer.aget_tuple(...)` or `await graph.ainvoke(...)`.",
     "output_type": "error",
     "traceback": [
      "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
      "\u001B[0;31mInvalidStateError\u001B[0m                         Traceback (most recent call last)",
      "Cell \u001B[0;32mIn[22], line 5\u001B[0m\n\u001B[1;32m      3\u001B[0m \u001B[38;5;28;01masync\u001B[39;00m \u001B[38;5;28;01mwith\u001B[39;00m AsyncSqliteSaver\u001B[38;5;241m.\u001B[39mfrom_conn_string(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124m./checkpoints.db\u001B[39m\u001B[38;5;124m\"\u001B[39m) \u001B[38;5;28;01mas\u001B[39;00m memory:\n\u001B[1;32m      4\u001B[0m     graph \u001B[38;5;241m=\u001B[39m builder\u001B[38;5;241m.\u001B[39mcompile(checkpointer\u001B[38;5;241m=\u001B[39mmemory)\n\u001B[0;32m----> 5\u001B[0m     coro \u001B[38;5;241m=\u001B[39m graph\u001B[38;5;241m.\u001B[39minvoke({\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mmessages\u001B[39m\u001B[38;5;124m\"\u001B[39m: [input_message]}, config)\n",
      "File \u001B[0;32m/opt/anaconda3/envs/ai_312/lib/python3.12/site-packages/langgraph/pregel/__init__.py:1600\u001B[0m, in \u001B[0;36mPregel.invoke\u001B[0;34m(self, input, config, stream_mode, output_keys, interrupt_before, interrupt_after, debug, **kwargs)\u001B[0m\n\u001B[1;32m   1598\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[1;32m   1599\u001B[0m     chunks \u001B[38;5;241m=\u001B[39m []\n\u001B[0;32m-> 1600\u001B[0m \u001B[38;5;28;01mfor\u001B[39;00m chunk \u001B[38;5;129;01min\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mstream(\n\u001B[1;32m   1601\u001B[0m     \u001B[38;5;28minput\u001B[39m,\n\u001B[1;32m   1602\u001B[0m     config,\n\u001B[1;32m   1603\u001B[0m     stream_mode\u001B[38;5;241m=\u001B[39mstream_mode,\n\u001B[1;32m   1604\u001B[0m     output_keys\u001B[38;5;241m=\u001B[39moutput_keys,\n\u001B[1;32m   1605\u001B[0m     interrupt_before\u001B[38;5;241m=\u001B[39minterrupt_before,\n\u001B[1;32m   1606\u001B[0m     interrupt_after\u001B[38;5;241m=\u001B[39minterrupt_after,\n\u001B[1;32m   1607\u001B[0m     debug\u001B[38;5;241m=\u001B[39mdebug,\n\u001B[1;32m   1608\u001B[0m     \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs,\n\u001B[1;32m   1609\u001B[0m ):\n\u001B[1;32m   1610\u001B[0m     \u001B[38;5;28;01mif\u001B[39;00m stream_mode \u001B[38;5;241m==\u001B[39m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mvalues\u001B[39m\u001B[38;5;124m\"\u001B[39m:\n\u001B[1;32m   1611\u001B[0m         latest \u001B[38;5;241m=\u001B[39m chunk\n",
      "File \u001B[0;32m/opt/anaconda3/envs/ai_312/lib/python3.12/site-packages/langgraph/pregel/__init__.py:1276\u001B[0m, in \u001B[0;36mPregel.stream\u001B[0;34m(self, input, config, stream_mode, output_keys, interrupt_before, interrupt_after, debug, subgraphs)\u001B[0m\n\u001B[1;32m   1272\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mcustom\u001B[39m\u001B[38;5;124m\"\u001B[39m \u001B[38;5;129;01min\u001B[39;00m stream_modes:\n\u001B[1;32m   1273\u001B[0m     config[CONF][CONFIG_KEY_STREAM_WRITER] \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mlambda\u001B[39;00m c: stream\u001B[38;5;241m.\u001B[39mput(\n\u001B[1;32m   1274\u001B[0m         ((), \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mcustom\u001B[39m\u001B[38;5;124m\"\u001B[39m, c)\n\u001B[1;32m   1275\u001B[0m     )\n\u001B[0;32m-> 1276\u001B[0m \u001B[38;5;28;01mwith\u001B[39;00m SyncPregelLoop(\n\u001B[1;32m   1277\u001B[0m     \u001B[38;5;28minput\u001B[39m,\n\u001B[1;32m   1278\u001B[0m     stream\u001B[38;5;241m=\u001B[39mStreamProtocol(stream\u001B[38;5;241m.\u001B[39mput, stream_modes),\n\u001B[1;32m   1279\u001B[0m     config\u001B[38;5;241m=\u001B[39mconfig,\n\u001B[1;32m   1280\u001B[0m     store\u001B[38;5;241m=\u001B[39mstore,\n\u001B[1;32m   1281\u001B[0m     checkpointer\u001B[38;5;241m=\u001B[39mcheckpointer,\n\u001B[1;32m   1282\u001B[0m     nodes\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mnodes,\n\u001B[1;32m   1283\u001B[0m     specs\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mchannels,\n\u001B[1;32m   1284\u001B[0m     output_keys\u001B[38;5;241m=\u001B[39moutput_keys,\n\u001B[1;32m   1285\u001B[0m     stream_keys\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mstream_channels_asis,\n\u001B[1;32m   1286\u001B[0m     debug\u001B[38;5;241m=\u001B[39mdebug,\n\u001B[1;32m   1287\u001B[0m ) \u001B[38;5;28;01mas\u001B[39;00m loop:\n\u001B[1;32m   1288\u001B[0m     \u001B[38;5;66;03m# create runner\u001B[39;00m\n\u001B[1;32m   1289\u001B[0m     runner \u001B[38;5;241m=\u001B[39m PregelRunner(\n\u001B[1;32m   1290\u001B[0m         submit\u001B[38;5;241m=\u001B[39mloop\u001B[38;5;241m.\u001B[39msubmit,\n\u001B[1;32m   1291\u001B[0m         put_writes\u001B[38;5;241m=\u001B[39mloop\u001B[38;5;241m.\u001B[39mput_writes,\n\u001B[1;32m   1292\u001B[0m         node_finished\u001B[38;5;241m=\u001B[39mconfig[CONF]\u001B[38;5;241m.\u001B[39mget(CONFIG_KEY_NODE_FINISHED),\n\u001B[1;32m   1293\u001B[0m     )\n\u001B[1;32m   1294\u001B[0m     \u001B[38;5;66;03m# enable subgraph streaming\u001B[39;00m\n",
      "File \u001B[0;32m/opt/anaconda3/envs/ai_312/lib/python3.12/site-packages/langgraph/pregel/loop.py:697\u001B[0m, in \u001B[0;36mSyncPregelLoop.__enter__\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m    695\u001B[0m         \u001B[38;5;28;01mraise\u001B[39;00m CheckpointNotLatest\n\u001B[1;32m    696\u001B[0m \u001B[38;5;28;01melif\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mcheckpointer:\n\u001B[0;32m--> 697\u001B[0m     saved \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mcheckpointer\u001B[38;5;241m.\u001B[39mget_tuple(\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mcheckpoint_config)\n\u001B[1;32m    698\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[1;32m    699\u001B[0m     saved \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mNone\u001B[39;00m\n",
      "File \u001B[0;32m/opt/anaconda3/envs/ai_312/lib/python3.12/site-packages/langgraph/checkpoint/sqlite/aio.py:160\u001B[0m, in \u001B[0;36mAsyncSqliteSaver.get_tuple\u001B[0;34m(self, config)\u001B[0m\n\u001B[1;32m    156\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[1;32m    157\u001B[0m     \u001B[38;5;66;03m# check if we are in the main thread, only bg threads can block\u001B[39;00m\n\u001B[1;32m    158\u001B[0m     \u001B[38;5;66;03m# we don't check in other methods to avoid the overhead\u001B[39;00m\n\u001B[1;32m    159\u001B[0m     \u001B[38;5;28;01mif\u001B[39;00m asyncio\u001B[38;5;241m.\u001B[39mget_running_loop() \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mloop:\n\u001B[0;32m--> 160\u001B[0m         \u001B[38;5;28;01mraise\u001B[39;00m asyncio\u001B[38;5;241m.\u001B[39mInvalidStateError(\n\u001B[1;32m    161\u001B[0m             \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mSynchronous calls to AsyncSqliteSaver are only allowed from a \u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[1;32m    162\u001B[0m             \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mdifferent thread. From the main thread, use the async interface.\u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[1;32m    163\u001B[0m             \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mFor example, use `await checkpointer.aget_tuple(...)` or `await \u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[1;32m    164\u001B[0m             \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mgraph.ainvoke(...)`.\u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[1;32m    165\u001B[0m         )\n\u001B[1;32m    166\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mRuntimeError\u001B[39;00m:\n\u001B[1;32m    167\u001B[0m     \u001B[38;5;28;01mpass\u001B[39;00m\n",
      "\u001B[0;31mInvalidStateError\u001B[0m: Synchronous calls to AsyncSqliteSaver are only allowed from a different thread. From the main thread, use the async interface.For example, use `await checkpointer.aget_tuple(...)` or `await graph.ainvoke(...)`."
     ]
    }
   ],
   "source": [
    "from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver\n",
    "\n",
    "async with AsyncSqliteSaver.from_conn_string(\"./checkpoints.db\") as memory:\n",
    "    graph = builder.compile(checkpointer=memory)\n",
    "    coro = graph.invoke({\"messages\": [input_message]}, config)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-12-02T09:49:30.225403Z",
     "start_time": "2024-12-02T09:49:30.087169Z"
    }
   },
   "id": "9e6a01ae1dd6438d",
   "execution_count": 22
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
